﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Antlr.Runtime;
using Antlr.Runtime.Tree;

namespace SqlAntlr
{
    public abstract class Column
    {
        // column information
        protected CommonTree expr;    // expression referring in the query
        protected String alias;       // name
        protected IView from;          // exposed by

        // depending columns. 
        protected List<Column.Value> dependant = new List<Column.Value>();

        protected Column(IView v, CommonTree e)
        {
            from = v;
            expr = e;
        }

        protected Column(IView v, CommonTree e, String a)
        {
            from = v;
            expr = e;
            setAlias(a);
        }

        public void setExpression(CommonTree e) { expr = e; }

        public void setAlias(String s)
        {
            alias = (s == null) ? null : s.ToLower();
        }
        public void setQuery(IView q) { from = q; }

        public void addDependant(Column.Value c) { dependant.Add(c); }
        public List<Column.Value> getDependants() { return dependant; }

        /**
         * @return real name of this column. only returns different value if 
         * this object is defined in concrete table.
         */
        public virtual String getRealName() { return alias; }

        /**
         * @return alias of this column. <code>null</code> if there is no alias
         */
        public virtual String getAlias() { return alias; }

        /**
         * get {@link edu.washington.db.cqms.common.sqlparser.IView IView} which
         * has this object in its SELECT clause
         */
        public IView getIView() { return from; }

        /**
         * get the AST node of expression corresponding to this column
         */
        public CommonTree getExpression() { return expr; }
        public List<Column.Value> getDependant() { return dependant; }

        public String getExpressionText(ITokenStream st)
        {
            return (expr == null) ? "nil" : st.ToString(expr.TokenStartIndex, expr.TokenStopIndex);
        }

        public String toString()
        {
            if (alias == null)
            {
                return String.Format("{0}.{1}", from.getAlias(), getRealName());
            }
            return String.Format("{0}.{1} AS {2}", from.getAlias(), getRealName(), alias);
        }

        public abstract bool isSame(Column o);

        protected bool compareColumns(IList<Column> a, IList<Column> b)
        {
            if (a.Count != b.Count)
                return false;

            IEnumerator<Column> i = a.GetEnumerator();
            IEnumerator<Column> j = b.GetEnumerator();

            while (i.MoveNext() && j.MoveNext())
            {
                Column c1 = i.Current;
                Column c2 = j.Current;
                if (!c1.isSame(c2))
                    return false;
            }

            return true;
        }

        ///////////////////////////////////////////////////////////////////////////

        /**
         * NOTE: on dependency analysis, star is expanded as following.
         * when no group-by clause present, it is replaced with key columns.
         * when group-by clause present, it is replaced with other columns
         *  except group-by attributes.
         */
        public class Star : Column
        {
            private IList<Column> columns;
            private IView starFrom;

            public Star(IView v, CommonTree t)
                : base(v, t, "*")
            {
                starFrom = v;
                columns = starFrom.getColumns(false);
                // FIXME: sort

                foreach (Column c in columns)
                {
                    dependant.AddRange(c.dependant);
                }
            }

            /**
             * @param r real relation of which columns will be selected. used for table columns
             */
            public Star(IView v, CommonTree t, IView r)
                : base(v, t, "*")
            {
                starFrom = r;
                columns = starFrom.getColumns(false);
                // FIXME: sort

                foreach (Column c in columns)
                {
                    dependant.AddRange(c.dependant);
                }
            }

            public IList<Column> getColumns() { return columns; }
            public IView getQuery() { return starFrom; }

            public override bool isSame(Column obj)
            {
                if (this == obj) return true;
                if (!(obj is Column.Star)) return false;
                Column.Star o = (Column.Star)obj;
                return compareColumns(columns, o.columns);
            }
        }

        /**
         * function
         */
        public class Function : Column
        {
            public enum Modifier
            {
                NONE,
                ALL,
                DISTINCT
            };

            String name;
            Modifier mod;
            IList<Column> args;

            public Function(IView v, String fn)
                : base(v, null, fn)
            {
                name = fn;
                mod = Modifier.NONE;
                args = new List<Column>().AsReadOnly();
            }

            public Function(IView v, String fn, Modifier m, IList<Column> args)
                : base(v, null, fn)
            {
                name = fn;
                mod = m;
                this.args = args;
                foreach (Column arg in args)
                {
                    dependant.AddRange(arg.dependant);
                }
            }

            public void setModifer(Modifier m) { mod = m; }

            public bool isAll() { return mod == Modifier.ALL; }
            public bool isDistinct() { return mod == Modifier.DISTINCT; }

            public String getFunctionName() { return name; }
            public int size() { return args.Count; }
            public IList<Column> getArgs() { return args; }
            public Column getArg(int pos) { return args[pos]; }

            public override bool isSame(Column obj)
            {
                if (this == obj) return true;
                if (!(obj is Column.Function)) return false;
                Column.Function o = (Column.Function)obj;

                if (!name.Equals(o.name) || mod != o.mod) return false;

                // now compare the arguments
                return compareColumns(args, o.args);
            }
        }


        static HashSet<String> aggrNames;

        static Column()
        {
            aggrNames = new HashSet<String>();
            aggrNames.Add("avg");
            aggrNames.Add("count");
            aggrNames.Add("min");
            aggrNames.Add("max");
            aggrNames.Add("stdev");
            aggrNames.Add("stdep");
            aggrNames.Add("sum");
            aggrNames.Add("var");
            aggrNames.Add("varp");
        }

        public static bool isAggregate(String n)
        {
            return aggrNames.Contains(n);
        }


        /**
         * subquery
         */
        public class Query : Column
        {
            SqlQuery query;

            public Query(IView v, SqlQuery q)
                : base(v, q.getStatement(), q.getAlias())
            {
                query = q;
            }


            public Query(IView v, SqlQuery q, String a)
                : base(v, q.getStatement(), a)
            {
                query = q;
            }

            public SqlQuery getQuery() { return query; }

            public override bool isSame(Column obj)
            {
                if (this == obj) return true;
                if (!(obj is Column.Query)) return false;
                Column.Query o = (Column.Query)obj;
                //return query.isSame(o.query);
                //FIXME
                return false;
            }
        }

        /**
         * generic expression
         */
        public class Expr : Column
        {
            public Expr(IView v, CommonTree e, String a) : base(v, e, a) { }
            public Expr(IView v, CommonTree e) : base(v, e) { }

            public override bool isSame(Column obj)
            {
                // FIXME
                return false;
            }
        }

        /**
         * table column
         */
        public class Value : Column
        {
            private String name;    // actual name -- from table
            private IView origin;    // origin
            private Column refer;   // refering column

            public Value(IView v, Column c)
                : base(v, null, c.getAlias())
            {
                if (c is Value)
                {
                    origin = ((Value)c).origin;
                    name = ((Value)c).name;
                }
                else
                {
                    origin = c.getIView();
                    name = c.getAlias();
                }

                dependant.AddRange(c.dependant);

                refer = c;
            }


            public Value(IView v, CommonTree e, Column c)
                : base(v, e, c.getAlias())
            {
                if (c is Value)
                {
                    origin = ((Value)c).origin;
                    name = ((Value)c).name;
                }
                else
                {
                    origin = c.getIView();
                    name = c.getAlias();
                }

                dependant.AddRange(c.dependant);

                refer = c;
            }

            public Value(IView v, CommonTree e, Column c, String a)
                : base(v, e, (a == null) ? c.getAlias() : a)
            {
                if (c is Value)
                {
                    origin = ((Value)c).origin;
                    name = ((Value)c).name;
                }
                else
                {
                    origin = c.getIView();
                    name = c.getAlias();
                }

                dependant.AddRange(c.dependant);

                refer = c;
            }

            public Value(IView v, String c)
                : base(v, null, c)
            {
                name = c;
                origin = v;

                // this is a column from a concrete table. depending on self
                addDependant(this);
            }

            public void setRealName(String s)
            {
                name = (s == null) ? null : s.ToLower();
            }
            public void setOrigin(IView q)
            {
                origin = q;
            }
            public override String getRealName() { return name; }
            public IView getOrigin() { return origin; }

            /**
             * Get direct source of this column object. returned object is defined
             * in views in FROM clause or nesting queries.
             *
             * @return column object where this column is referring to.
             */
            public Column getReferer() { return refer; }

            /**
             * recursively track refer relationship and return the source of
             * column.
             *
             * @return originating column object. return identity object if
             * this object is the source.
             */
            public Column getRealReferer()
            {
                Column refx = this;

                while (refx != null && refx is Value && ((Value)refx).refer != null)
                {
                    refx = ((Value)refx).refer;
                }
                return refx;
            }

            /**
             * is this column object source?
             */
            public bool isConcrete()
            {
                return this == getRealReferer();
            }

            public override bool isSame(Column obj)
            {
                if (this == obj) return true;
                if (!(obj is Column.Value)) return false;
                Column.Value o = (Column.Value)obj;

                Column realThis = getRealReferer();
                Column realOther = o.getRealReferer();

                // if both of them are values, must be handled here.
                if (realThis is Column.Value
                        && realOther is Column.Value)
                {
                    Column.Value r1 = (Column.Value)realThis;
                    Column.Value r2 = (Column.Value)realOther;

                    // must come from the same relation and same column
                    return r1.getOrigin().getRealName().Equals(r2.getOrigin().getRealName()) && r1.getRealName().Equals(r2.getRealName());
                }
                else
                {
                    // otherwise, toss it to equals()
                    return realThis.isSame(realOther);
                }
            }
        }

        /**
         * constant
         */
        public class Constant : Column
        {
            String text;
            Object value;

            public Constant(IView v, CommonTree e, String t, Object o)
                : base(v, e, null)
            {
                text = t;
                value = o;
            }

            public Object getConstant() { return value; }
            public String getText() { return text; }

            public override String ToString()
            {
                return "CONST_" + value;
            }

            public override bool isSame(Column obj)
            {
                if (this == obj) return true;
                if (!(obj is Column.Constant)) return false;
                Column.Constant o = (Column.Constant)obj;
                return value.Equals(o.value);
            }

        }

        /**
         * array
         */
        public class Array : Column
        {
            IList<Column> array;

            public Array(IView v, CommonTree e, IList<Column> arr)
                : base(v, e, null)
            {
                array = arr;
                // FIXME: sort them
            }

            public int size() { return array.Count; }
            public bool isEmpty() { return array.Count == 0; }
            public Column get(int i) { return array[i]; }
            public IList<Column> getAll() { return array; }

            public override bool isSame(Column obj)
            {
                if (this == obj) return true;
                if (!(obj is Column.Array)) return false;
                Column.Array o = (Column.Array)obj;
                return compareColumns(array, o.array);
            }
        }
    }
}
